AWS CloudFormationで使える4種類のヘルパースクリプトについて使い方と機能をまとめてみた
CloudFormationのヘルパースクリプトは スタック内のEC2インスタンスの構築・変更等を便利にする機能を提供しています。
ヘルパースクリプトは全部で以下の4種類がありますが、 それぞれ実際にはどのように記述する必要があり、またどんな動作をするのか、確かめてみました。
cfn-init: リソースメタデータの取得と解釈、パッケージのインストール、ファイルの作成、およびサービスの開始で使用します。
cfn-signal: CreationPolicy または WaitCondition でシグナルを送信するために使用し、前提となるリソースやアプリケーションの準備ができたときに、スタックの他のリソースを同期できるようにします。
cfn-get-metadata: 特定のキーへのリソースまたはパスのメタデータを取得するために使用します。
cfn-hup: メタデータへの更新を確認し、変更が検出されたときにカスタムフックを実行するために使用します。
CloudFormation ヘルパースクリプトリファレンス - AWS CloudFormation
やってみた
検証用テンプレート(基本)
以下のCloudFormationテンプレートを基本に進めていきます。 Amazon Linux2のまっさらなEC2インスタンスを1台と、セキュリティグループを作成しています。 まだヘルパースクリプトは利用していません。
AWSTemplateFormatVersion: "2010-09-09" Description: Cfn Helper Script Sample Parameters: VpcId: Type: 'AWS::EC2::VPC::Id' Description: Your default VPC Id SubnetId: Type: 'AWS::EC2::Subnet::Id' Description: SubnetId in your default VPC KeyName: Type: 'AWS::EC2::KeyPair::KeyName' Description: Name of an existing EC2 KeyPair to enable SSH access to the instances FromIpAddress: Type: String Description: The IP address range that can be used to SSH and HTTP to the EC2 instances Default: 0.0.0.0/0 AmiId: Description: AMI Id Type: String Default: ami-0a1c2ec61571737db Resources: ServerInstance: Type: AWS::EC2::Instance Properties: ImageId: !Ref AmiId InstanceType: t3.micro SecurityGroupIds: - !Ref InstanceSecurityGroup KeyName: !Ref KeyName SubnetId : !Ref SubnetId InstanceSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Enable SSH access and HTTP access on the inbound port SecurityGroupIngress: - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: !Ref FromIpAddress - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: !Ref FromIpAddress VpcId: !Ref VpcId
cfn-init
リソースメタデータの取得と解釈、パッケージのインストール、ファイルの作成、およびサービスの開始で使用します。
cfn-initは、CloudFormationリソースのメタデータ AWS::CloudFormation::Init
セクションに記述されたコンフィグを取得し
その記述のとおりにEC2インスタンス内でパッケージのインストールやファイルの作成、サービスの開始などを実行するヘルパースクリプトです。
リファレンスを参照しつつ、試しに書いてみました。
- インスタンスのメタデータ内
AWS::CloudFormation::Init
セクションに以下のコンフィグを記述。(リファレンス)- httpdのインストール
- /var/www/html/index.html の配置
- httpdの起動設定
- インスタンスのUserData内でcfn-init スクリプトを実行するよう指定。 これがメタデータに記されたコンフィグを読み取り、処理してくれます。(リファレンス)
Resources: ServerInstance: Type: AWS::EC2::Instance Metadata: Comment: Install a simple web app AWS::CloudFormation::Init: config: packages: yum: httpd: [] files: /var/www/html/index.html: content: !Sub | <p>Hello!</p> mode: '000644' owner: root group: root services: sysvinit: httpd: enabled: 'true' ensureRunning: 'true' Properties: ImageId: !Ref AmiId InstanceType: t3.micro SecurityGroupIds: - !Ref InstanceSecurityGroup KeyName: !Ref KeyName SubnetId : !Ref SubnetId UserData: Fn::Base64: !Sub | #!/bin/bash -xe /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource ServerInstance \ --region ${AWS::Region} InstanceSecurityGroup: Type: 'AWS::EC2::SecurityGroup'
テンプレートファイル全体はこちら(デプロイ時には東京リージョンの、デフォルトVPCを指定してください)
このCloudFormationテンプレートから起動したインスタンスは メタデータで指定したとおりにhttpdが起動しており、 テンプレート内で作成したindex.htmlがブラウザから確認できました!
2020/7/7 追記:
今はSSMのState Managerを利用する方法が推奨されているようです。
CloudFormation で cfn-init に代えて State Manager を利用する方法とその利点 | Amazon Web Services ブログ
cfn-hup
メタデータへの更新を確認し、変更が検出されたときにカスタムフックを実行するために使用します。
cfn-hupは、CloudFormationリソースのメタデータの変更を検知して ユーザの指定した動作を実行するために待ち受けるデーモンです。
事前設定
リファレンスを参考にcfn-hupデーモンの設定ファイルを作成し、 インスタンス内でcfn-hupデーモンが常時起動するように設定します。 メタデータの更新を1分間隔で確認し、変更が検出されたらcfn-initを実行する設定を記述しています。
Resources: ServerInstance: Type: AWS::EC2::Instance Metadata: Comment: Install a simple web app AWS::CloudFormation::Init: config: packages: yum: httpd: [] files: /var/www/html/index.html: content: !Sub | <p>Hello!</p> mode: '000644' owner: root group: root /etc/cfn/cfn-hup.conf: content: !Sub | [main] stack=${AWS::StackName} region=${AWS::Region} interval=1 mode: '000400' owner: root group: root /etc/cfn/hooks.d/cfn-auto-reloader.conf: content: !Sub | [cfn-auto-reloader-hook] triggers=post.update path=Resources.ServerInstance.Metadata.AWS::CloudFormation::Init action=/opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource ServerInstance \ --region ${AWS::Region} runas=root services: sysvinit: httpd: enabled: 'true' ensureRunning: 'true' cfn-hup: enabled: 'true' ensureRunning: 'true' files: - /etc/cfn/cfn-hup.conf - /etc/cfn/hooks.d/cfn-auto-reloader.conf
テンプレートファイル全体はこちら(ユーザーデータを実行する必要があるため、新規にスタックを作り直してください)
動作確認
CloudFormationテンプレート内のメタデータに変更をかけてみて、実際にcfn-hupが動くことを確かめます。 試しにメタデータ内で作成したindex.htmlファイルの中身を更新してみました。
files: /var/www/html/index.html: content: !Sub | <p>Welcome!!!!</p>
UpdateStackを実行します。
変更はメタデータの内容のみで特にインスタンスの置換や再起動などは発生しませんが、 暫く待ってみるとcfn-hupによりcfn-auto-reloader-hookがトリガーされたようで 無事コンテンツが更新されたことが確認できました。
↓
cfn-get-metadata
特定のキーへのリソースまたはパスのメタデータを取得するために使用します。
メタデータの情報を取得するためのスクリプトです。
先程cfn-hupの検証のため起動したEC2インスタンス内で cfn-get-metadata
を実行してみたところ、
以下のようにCloudFormationリソースのメタデータの内容が出力されました。(リファレンス)
[ec2-user@ip-172-31-20-XX ~]$ sudo /opt/aws/bin/cfn-get-metadata \ > --stack cfn-helper-scripts2 \ > --resource ServerInstance \ > --region ap-northeast-1 { "Comment": "Install a simple web app", "AWS::CloudFormation::Init": { "config": { "files": { "/etc/cfn/cfn-hup.conf": { "owner": "root", "content": "[main]\nstack=cfn-helper-scripts-2\nregion=ap-northeast-1\ninterval=1\n", "group": "root", "mode": "000400" }, "/etc/cfn/hooks.d/cfn-auto-reloader.conf": { "content": "[cfn-auto-reloader-hook]\ntriggers=post.update\npath=Resources.ServerInstance.Metadata.AWS::CloudFormation::Init\naction=/opt/aws/bin/cfn-init -v \\\n --stack cfn-helper-scripts-2 \\\n --resource ServerInstance \\\n --region ap-northeast-1\nrunas=root\n" }, "/var/www/html/index.html": { "owner": "root", "content": "<p>Welcome!!!!</p>\n", "group": "root", "mode": "000644" } }, "services": { "sysvinit": { "httpd": { "ensureRunning": "true", "enabled": "true" }, "cfn-hup": { "files": [ "/etc/cfn/cfn-hup.conf", "/etc/cfn/hooks.d/cfn-auto-reloader.conf" ], "ensureRunning": "true", "enabled": "true" } } }, "packages": { "yum": { "httpd": [] } } } } }
cfn-signal
CreationPolicy または WaitCondition でシグナルを送信するために使用し、前提となるリソースやアプリケーションの準備ができたときに、スタックの他のリソースを同期できるようにします。
cfn-signalは、 EC2リソースが正常に作成/更新されたかどうかを示すシグナルをCloudFormation に送信するスクリプトです。
シグナルはCloudFormationの CreationPolicy
,UpdatePolicy
属性などで使われています。
こちらの動作を確かめるために、テンプレートに以下の設定を加えてみます。
- EC2インスタンスリソースの
CreationPolicy
を指定し、タイムアウトをめちゃくちゃシビア(10秒)に設定。 (リファレンス) - インスタンスのUserData内でcfn-init スクリプトの成否をシグナルとしてスタックに送信するよう設定。 (リファレンス)
Resources: ServerInstance: Type: AWS::EC2::Instance CreationPolicy: ResourceSignal: Count: 1 Timeout: PT10S Metadata: Comment: Install a simple web app
UserData: Fn::Base64: !Sub | #!/bin/bash -xe /opt/aws/bin/cfn-init -v \ --stack ${AWS::StackName} \ --resource ServerInstance \ --region ${AWS::Region} # signal the status from cfn-init /opt/aws/bin/cfn-signal -e $? \ --stack ${AWS::StackName} \ --resource ServerInstance \ --region ${AWS::Region} InstanceSecurityGroup: Type: 'AWS::EC2::SecurityGroup'
テンプレート全体はこちら(CreationPolicyの判定を動かすため、スタックは更新ではなく新規に作り直してください)
これでリソースの作成開始から10秒以内にCloudFormationにシグナルが飛ばない場合にはスタックがロールバックされるはず…。
実際に動かしてみたところ、指定した期間内にシグナルを受信しなかったためタイムアウトが発生し リソースの作成は失敗扱いとなりました。
なお、タイムアウトの10秒から10分間に伸ばしてみたところ 作成から1分弱でシグナルの受信を検知した旨のメッセージが表示され、リソースの作成が完了していました。
使いみちとしては、AutoScalingグループ内のEC2インスタンスの更新時などに 新インスタンスのデプロイに失敗した(=成功シグナルを時間内に受信できなかった)場合はインスタンスを更新せずにロールバックする、などがありそうです。
まとめ
以上、CloudFormationの4種類のヘルパースクリプトをそれぞれ使ってみました。
何らかのお役に立てれば幸いです。